package com.github.mikephil.charting.renderer;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Path;

import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.LimitLine.LimitLabelPosition;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.components.YAxis.YAxisLabelPosition;
import com.github.mikephil.charting.utils.PointD;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;

import java.util.List;

public class YAxisRenderer extends AxisRenderer {

	protected YAxis mYAxis;

	public YAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
		super(viewPortHandler, trans);

		this.mYAxis = yAxis;

		mAxisLabelPaint.setColor(Color.BLACK);
		mAxisLabelPaint.setTextSize(Utils.convertDpToPixel(10f));
	}

	/**
	 * Computes the axis values.
	 * 
	 * @param yMin
	 *            - the minimum y-value in the data object for this axis
	 * @param yMax
	 *            - the maximum y-value in the data object for this axis
	 */
	public void computeAxis(float yMin, float yMax) {

		// calculate the starting and entry point of the y-labels (depending on
		// zoom / contentrect bounds)
		if (mViewPortHandler.contentWidth() > 10 && !mViewPortHandler.isFullyZoomedOutY()) {

			PointD p1 = mTrans.getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop());
			PointD p2 = mTrans.getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentBottom());

			if (!mYAxis.isInverted()) {
				yMin = (float) p2.y;
				yMax = (float) p1.y;
			} else {

				yMin = (float) p1.y;
				yMax = (float) p2.y;
			}
		}

		computeAxisValues(yMin, yMax);
	}

	/**
	 * Sets up the y-axis labels. Computes the desired number of labels between the two given extremes. Unlike the
	 * papareXLabels() method, this method needs to be called upon every refresh of the view.
	 * 
	 * @return
	 */
	protected void computeAxisValues(float min, float max) {

		float yMin = min;
		float yMax = max;

		int labelCount = mYAxis.getLabelCount();
		double range = Math.abs(yMax - yMin);

		if (labelCount == 0 || range <= 0) {
			mYAxis.mEntries = new float[] {};
			mYAxis.mEntryCount = 0;
			return;
		}

        double rawInterval = range / labelCount;
        double interval = Utils.roundToNextSignificant(rawInterval);
        double intervalMagnitude = Math.pow(10, (int) Math.log10(interval));
        int intervalSigDigit = (int) (interval / intervalMagnitude);
        if (intervalSigDigit > 5) {
            // Use one order of magnitude higher, to avoid intervals like 0.9 or
            // 90
            interval = Math.floor(10 * intervalMagnitude);
        }

        // force label count
		if (mYAxis.isForceLabelsEnabled()) {

            float step = (float) range / (float) (labelCount-1);
            mYAxis.mEntryCount = labelCount;

            if (mYAxis.mEntries.length < labelCount) {
                // Ensure stops contains at least numStops elements.
                mYAxis.mEntries = new float[labelCount];
            }

            float v = min;

            for(int i = 0; i < labelCount; i++) {
                mYAxis.mEntries[i] = v;
                v += step;
            }

            // no forced count
		} else {

			// if the labels should only show min and max
			if (mYAxis.isShowOnlyMinMaxEnabled()) {

				mYAxis.mEntryCount = 2;
				mYAxis.mEntries = new float[2];
				mYAxis.mEntries[0] = yMin;
				mYAxis.mEntries[1] = yMax;

			} else {

				double first = Math.ceil(yMin / interval) * interval;
				double last = Utils.nextUp(Math.floor(yMax / interval) * interval);

				double f;
				int i;
				int n = 0;
				for (f = first; f <= last; f += interval) {
					++n;
				}

				mYAxis.mEntryCount = n;

				if (mYAxis.mEntries.length < n) {
					// Ensure stops contains at least numStops elements.
					mYAxis.mEntries = new float[n];
				}

				for (f = first, i = 0; i < n; f += interval, ++i) {
					mYAxis.mEntries[i] = (float) f;
				}
			}
		}

        // set decimals
        if (interval < 1) {
            mYAxis.mDecimals = (int) Math.ceil(-Math.log10(interval));
        } else {
            mYAxis.mDecimals = 0;
        }
	}

	/**
	 * draws the y-axis labels to the screen
	 */
	@Override
	public void renderAxisLabels(Canvas c) {

		if (!mYAxis.isEnabled() || !mYAxis.isDrawLabelsEnabled())
			return;

		float[] positions = new float[mYAxis.mEntryCount * 2];

		for (int i = 0; i < positions.length; i += 2) {
			// only fill y values, x values are not needed since the y-labels
			// are
			// static on the x-axis
			positions[i + 1] = mYAxis.mEntries[i / 2];
		}

		mTrans.pointValuesToPixel(positions);

		mAxisLabelPaint.setTypeface(mYAxis.getTypeface());
		mAxisLabelPaint.setTextSize(mYAxis.getTextSize());
		mAxisLabelPaint.setColor(mYAxis.getTextColor());

		float xoffset = mYAxis.getXOffset();
		float yoffset = Utils.calcTextHeight(mAxisLabelPaint, "A") / 2.5f + mYAxis.getYOffset();

		AxisDependency dependency = mYAxis.getAxisDependency();
		YAxisLabelPosition labelPosition = mYAxis.getLabelPosition();

		float xPos = 0f;

		if (dependency == AxisDependency.LEFT) {

			if (labelPosition == YAxisLabelPosition.OUTSIDE_CHART) {
				mAxisLabelPaint.setTextAlign(Align.RIGHT);
				xPos = mViewPortHandler.offsetLeft() - xoffset;
			} else {
				mAxisLabelPaint.setTextAlign(Align.LEFT);
				xPos = mViewPortHandler.offsetLeft() + xoffset;
			}

		} else {

			if (labelPosition == YAxisLabelPosition.OUTSIDE_CHART) {
				mAxisLabelPaint.setTextAlign(Align.LEFT);
				xPos = mViewPortHandler.contentRight() + xoffset;
			} else {
				mAxisLabelPaint.setTextAlign(Align.RIGHT);
				xPos = mViewPortHandler.contentRight() - xoffset;
			}
		}

		drawYLabels(c, xPos, positions, yoffset);
	}

	@Override
	public void renderAxisLine(Canvas c) {

		if (!mYAxis.isEnabled() || !mYAxis.isDrawAxisLineEnabled())
			return;

		mAxisLinePaint.setColor(mYAxis.getAxisLineColor());
		mAxisLinePaint.setStrokeWidth(mYAxis.getAxisLineWidth());

		if (mYAxis.getAxisDependency() == AxisDependency.LEFT) {
			c.drawLine(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop(), mViewPortHandler.contentLeft(),
					mViewPortHandler.contentBottom(), mAxisLinePaint);
		} else {
			c.drawLine(mViewPortHandler.contentRight(), mViewPortHandler.contentTop(), mViewPortHandler.contentRight(),
					mViewPortHandler.contentBottom(), mAxisLinePaint);
		}
	}

	/**
	 * draws the y-labels on the specified x-position
	 * 
	 * @param fixedPosition
	 * @param positions
	 */
	protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset) {

		// draw
		for (int i = 0; i < mYAxis.mEntryCount; i++) {

			String text = mYAxis.getFormattedLabel(i);

			if (!mYAxis.isDrawTopYLabelEntryEnabled() && i >= mYAxis.mEntryCount - 1)
				return;

			c.drawText(text, fixedPosition, positions[i * 2 + 1] + offset, mAxisLabelPaint);
		}
	}

	@Override
	public void renderGridLines(Canvas c) {

		if (!mYAxis.isDrawGridLinesEnabled() || !mYAxis.isEnabled())
			return;

		// pre alloc
		float[] position = new float[2];

		mGridPaint.setColor(mYAxis.getGridColor());
		mGridPaint.setStrokeWidth(mYAxis.getGridLineWidth());
		mGridPaint.setPathEffect(mYAxis.getGridDashPathEffect());

		Path gridLinePath = new Path();

		// draw the horizontal grid
		for (int i = 0; i < mYAxis.mEntryCount; i++) {

			position[1] = mYAxis.mEntries[i];
			mTrans.pointValuesToPixel(position);

			gridLinePath.moveTo(mViewPortHandler.offsetLeft(), position[1]);
			gridLinePath.lineTo(mViewPortHandler.contentRight(), position[1]);

			// draw a path because lines don't support dashing on lower android versions
			c.drawPath(gridLinePath, mGridPaint);

			gridLinePath.reset();
		}
	}

	/**
	 * Draws the LimitLines associated with this axis to the screen.
	 * 
	 * @param c
	 */
	@Override
	public void renderLimitLines(Canvas c) {

		List<LimitLine> limitLines = mYAxis.getLimitLines();

		if (limitLines == null || limitLines.size() <= 0)
			return;

		float[] pts = new float[2];
		Path limitLinePath = new Path();

		for (int i = 0; i < limitLines.size(); i++) {

			LimitLine l = limitLines.get(i);

			mLimitLinePaint.setStyle(Paint.Style.STROKE);
			mLimitLinePaint.setColor(l.getLineColor());
			mLimitLinePaint.setStrokeWidth(l.getLineWidth());
			mLimitLinePaint.setPathEffect(l.getDashPathEffect());

			pts[1] = l.getLimit();

			mTrans.pointValuesToPixel(pts);

			limitLinePath.moveTo(mViewPortHandler.contentLeft(), pts[1]);
			limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);

			c.drawPath(limitLinePath, mLimitLinePaint);
			limitLinePath.reset();
			// c.drawLines(pts, mLimitLinePaint);

			String label = l.getLabel();

			// if drawing the limit-value label is enabled
			if (label != null && !label.equals("")) {

				mLimitLinePaint.setStyle(l.getTextStyle());
				mLimitLinePaint.setPathEffect(null);
				mLimitLinePaint.setColor(l.getTextColor());
				mLimitLinePaint.setStrokeWidth(0.5f);
				mLimitLinePaint.setTextSize(l.getTextSize());

				final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
				float xOffset = Utils.convertDpToPixel(4f);
				float yOffset = l.getLineWidth() + labelLineHeight;

				final LimitLine.LimitLabelPosition position = l.getLabelPosition();

				if (position == LimitLine.LimitLabelPosition.RIGHT_TOP) {

					mLimitLinePaint.setTextAlign(Align.RIGHT);
					c.drawText(label,
							mViewPortHandler.contentRight() - xOffset,
							pts[1] - yOffset + labelLineHeight, mLimitLinePaint);

				} else if (position == LimitLine.LimitLabelPosition.RIGHT_BOTTOM) {

					mLimitLinePaint.setTextAlign(Align.RIGHT);
					c.drawText(label,
							mViewPortHandler.contentRight() - xOffset,
							pts[1] + yOffset, mLimitLinePaint);

				} else if (position == LimitLine.LimitLabelPosition.LEFT_TOP) {

					mLimitLinePaint.setTextAlign(Align.LEFT);
					c.drawText(label,
							mViewPortHandler.contentLeft() + xOffset,
							pts[1] - yOffset + labelLineHeight, mLimitLinePaint);

				} else {

					mLimitLinePaint.setTextAlign(Align.LEFT);
					c.drawText(label,
							mViewPortHandler.offsetLeft() + xOffset,
							pts[1] + yOffset, mLimitLinePaint);
				}
			}
		}
	}
}
